package net.jxta.myjxta.plugin;


import net.jxta.myjxta.util.Env;
import net.jxta.myjxta.util.Logging;
import net.jxta.myjxta.util.Resources;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * Scenario: There is a local plugin directory that holds multiple plugin jars
 */
public final class PluginFetcher {
    ArrayList<ArrayList<URL>> m_possiblePluginJars = null;
    private static final Logger LOG = Logger.getLogger(PluginFetcher.class.getName());

    public static Plugin[] getValidPluginInstances(PluginJar[] p_pluginJars) {
        ArrayList<Plugin> result = new ArrayList<Plugin>();
        for (PluginJar pluginJar : p_pluginJars) {
            ArrayList<String> fileList = pluginJar.getEntryPointFileList();

            for (String fileName : fileList) {
                Class<?> pluginClass = null;

                try {
                    pluginClass = new PluginClassLoader(pluginJar.getJarUrls(),
                            PluginClassLoader.class.getClassLoader()).loadClass(fileName);
                }
                catch (ClassNotFoundException e) {
                    // e.printStackTrace();
                    if (LOG.isLoggable(Level.INFO)) {
                        LOG.info(e.getMessage());
                    }
                }
                if (pluginClass != null &&
                        Plugin.class.isAssignableFrom(pluginClass)) {
                    try {
                        Plugin plugin = (Plugin) pluginClass.newInstance();

                        result.add(plugin);
                        LOG.log(Level.FINE,
                                "PluginFetcher: loaded:" + pluginClass);
                    }
                    catch (NoClassDefFoundError e) {
                        logError(pluginClass, e);
                    }
                    catch (IllegalStateException e) {
                        logError(pluginClass, e);
                    }
                    catch (InstantiationException e) {
                        logError(pluginClass, e);
                    }
                    catch (IllegalAccessException e) {
                        logError(pluginClass, e);
                    }
                    catch (Throwable e) {
                        logError(pluginClass, e);
                    }
                }
            }
        }
        return result.toArray(new Plugin[result.size()]);
    }

    private static void logError(Class<?> pluginClass, Throwable e) {
        LOG.log(Logging.STATUS_LEVEL, "failed to load:" + pluginClass + e.toString());
    }

    public static PluginJar[] getInternalPlugins() {
        /**
         * This one is only temporary (to get the internal plugins as well)
         */
        URL classUrl = Resources.class.getResource(Env.CONSTANTS);
        LOG.log(java.util.logging.Level.INFO,
                "Internal location:" + classUrl.toExternalForm());
        String path = classUrl.toExternalForm();
        String classRoot = path.substring(0, path.indexOf(Env.CONSTANTS));
        if (!classRoot.endsWith("!")) {
            //classfile mode --> url-loader wants a trailing slash (directory)
            // this mode is for debugging only
            classRoot = classRoot + File.separator;
        } else {
            //jar file mode --> url classloader needs the jarfile
            classRoot = classUrl.getPath();
            classRoot = classRoot.substring(0, classRoot.lastIndexOf("!"));
            LOG.log(Logging.STATUS_LEVEL, "JAR FILE:" + classRoot);
        }
        URL internalRoot;
        try {
            internalRoot = new URL(classRoot);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return new PluginJar[0];
        }
        ArrayList<String> corePlugins = new ArrayList<String>();

        /**
         *Hand made list of internal plugin classes
         *internal means: in the core myjxta class path
         *(i know... an ugly hack but its only for debugging purpose)
         */
//        corePlugins.add("net.jxta.myjxta.plugins.meerkatdemo.MeerkatDemoPlugin");
        corePlugins.add("net.jxta.myjxta.plugins.groupchat.GroupChatPlugin");
        corePlugins.add("net.jxta.myjxta.plugins.secureonetoone.SecureOneToOnePlugin");
        corePlugins.add("net.jxta.myjxta.plugins.diagnostic.GroupChatTracePlugin");
        corePlugins.add("net.jxta.myjxta.plugins.filetransfer.FileTransferPlugin");
        corePlugins.add("net.jxta.myjxta.plugins.example.SimpleExamplePlugin");
        corePlugins.add("net.jxta.myjxta.plugins.example.SimpleRosterPlugin");
        corePlugins.add("net.jxta.myjxta.plugins.tictactoe.TicTacToePlugin");
        corePlugins.add("net.jxta.myjxta.plugins.vojxta.VoicePlugin");
        corePlugins.add("net.jxta.myjxta.plugins.vijxta.VideoPlugin");
        corePlugins.add("net.jxta.myjxta.plugins.share.CmsFileSharingPlugin");


        return new PluginJar[]{new PluginJar(new URL[]{internalRoot}, corePlugins)};
    }

    public static class PluginJar {
        private final URL m_jarUrl[];
        private final ArrayList<String> pluginsClassNames;

        public PluginJar(URL[] p_jarUrl, ArrayList<String> p_classFileList) {
            m_jarUrl = p_jarUrl;
            pluginsClassNames = p_classFileList;
        }

        public URL[] getJarUrls() {
            return m_jarUrl;
        }

        public ArrayList<String> getEntryPointFileList() {
            return pluginsClassNames;
        }
    }

    /**
     */
    public PluginFetcher() {
        String myjxtaHome = System.getProperty(Env.MYJXTA, Env.getHome().getPath());


        ArrayList<ArrayList<URL>> allPlugins = new ArrayList<ArrayList<URL>>();

        File f = new File(myjxtaHome, "plugins");


        LOG.log(java.util.logging.Level.FINE,
                "external plugin directory:" + f.getAbsolutePath());
        if (f.isDirectory()) {
            File[] pluginDirs = f.listFiles(new DirectoryFilter());
            for (File pluginDir1 : pluginDirs) {
                ArrayList<URL> jarsForPlugin = new ArrayList<URL>();
                File[] files = pluginDir1.listFiles(new JarFileFilter());
                for (File file : files) {
                    try {
                        jarsForPlugin.add(file.toURI().toURL());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (jarsForPlugin.size() > 0)
                    allPlugins.add(jarsForPlugin);
            }


        }


        m_possiblePluginJars = allPlugins;
    }


    public static PluginJar[] getExternalPluginJars() {
        PluginFetcher f = new PluginFetcher();
        ArrayList<ArrayList<URL>> urls = f.geJars();
        ArrayList<PluginJar> pluginJars = new ArrayList<PluginJar>();
        for (ArrayList<URL> urlList : urls) {
            URL[] url = urlList.toArray(new URL[urlList.size()]);
            try {
                ArrayList<String> oneJarList = getPluginClasses(url);
                if (oneJarList != null) {
                    PluginJar pluginJar = new PluginJar(url, oneJarList);
                    pluginJars.add(pluginJar);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return pluginJars.toArray(new PluginJar[pluginJars.size()]);
    }

    private static final Pattern m = Pattern.compile("/");

    private static ArrayList<String> getPluginClasses(URL[] p_url) throws IOException {
        ArrayList<String> result = new ArrayList<String>();
        for (URL aP_url : p_url) {
            JarFile jar = new JarFile(new File(aP_url.getFile()));
            Enumeration<JarEntry> enumeration = jar.entries();
            while (enumeration.hasMoreElements()) {
                JarEntry o = enumeration.nextElement();
                String name = o.getName();
                if (name.startsWith("net/jxta/myjxta/plugins/") && name.endsWith("Plugin.class")) {
                    System.out.println(o);
                    name = name.substring(0, name.indexOf(".class"));
                    name = m.matcher(name).replaceAll(".");
                    System.out.println("PluginClass:" + name);
                    result.add(name);
                }
            }
        }
        return result;
    }

    private ArrayList<ArrayList<URL>> geJars() {
        return m_possiblePluginJars;
    }

    public static void main(String[] args) {
        getExternalPluginJars();
    }

    private static class JarFileFilter implements FileFilter {
        public boolean accept(File pathname) {
            return !pathname.isDirectory() && pathname.getAbsolutePath().endsWith(".jar");
        }
    }

    private static class DirectoryFilter implements FileFilter {
        public boolean accept(File pathname) {
            return pathname.isDirectory();
        }
    }
}
